using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

using MSFTExcel = Microsoft.Office.Interop.Excel;

namespace TcKs.MSOffice.WrapperAssembliesCodeGenerator {
	/// <summary>
	/// Contains entry point of application.
	/// </summary>
	class Program {
		/// <summary>
		/// Default namespace for .NET wrapper.
		/// </summary>
		public const string DEFAULT_NAMESPACE__MSOFFICE_CORE = "TcKs.MSOffice";
		static void Main( string[] args ) {
			//GenerateEnumTypesForAssembly_Core();
			GenerateEnumTypesForAssembly_Excel();
			//GenerateEnumTypesForAssembly_Word();
			//Test_GenerateCodeForType();
		}

		/// <summary>
		/// Root directory path for generated source codes.
		/// </summary>
		const string SOURCE_CODE_ROOT_DIR_PATH = @"E:\+InDev\MsOfficeForNet";
		/// <summary>
		/// Directory name of "Common" project for MS Office wrapper.
		/// </summary>
		const string SOURCE_CODE_COMMON_DIR_NAME = "01 - Common";
		/// <summary>
		/// Directory name of "Excel" project for MS Office wrapper.
		/// </summary>
		const string SOURCE_CODE_EXCEL_DIR_NAME = "02 - Excel";
		/// <summary>
		/// Directory name of "Word" project for MS Office wrapper.
		/// </summary>
		const string SOURCE_CODE_WORD_DIR_NAME = "03 - Word";
		/// <summary>
		/// Directory name of "Outlook" project for MS Office wrapper.
		/// </summary>
		const string SOURCE_CODE_OUTLOOK_DIR_NAME = "04 - Outlook";

		/// <summary>
		/// Returns full path for "Common" project for MS Office wrapper.
		/// </summary>
		/// <returns></returns>
		static string GetDirPathFor_Common() {
			return System.IO.Path.Combine( SOURCE_CODE_ROOT_DIR_PATH, SOURCE_CODE_COMMON_DIR_NAME );
		}
		/// <summary>
		/// Returns full path for "Excel" project for MS Office wrapper.
		/// </summary>
		/// <returns></returns>
		static string GetDirPathFor_Excel() {
			return System.IO.Path.Combine( SOURCE_CODE_ROOT_DIR_PATH, SOURCE_CODE_EXCEL_DIR_NAME );
		}
		/// <summary>
		/// Returns full path for "Word" project for MS Office wrapper.
		/// </summary>
		/// <returns></returns>
		static string GetDirPathFor_Word() {
			return System.IO.Path.Combine( SOURCE_CODE_ROOT_DIR_PATH, SOURCE_CODE_WORD_DIR_NAME );
		}
		/// <summary>
		/// Returns full path for "Outlook" project for MS Office wrapper.
		/// </summary>
		/// <returns></returns>
		static string GetDirPathFor_Outlook() {
			return System.IO.Path.Combine( SOURCE_CODE_ROOT_DIR_PATH, SOURCE_CODE_OUTLOOK_DIR_NAME );
		}

		/// <summary>
		/// Generates enum types of core assemblies for MS Office wrapper.
		/// </summary>
		static void GenerateEnumTypesForAssembly_Core() {
			string dirPath = GetDirPathFor_Common();
			GenerateEnumTypesForAssembly( typeof( stdole.Font ).Assembly, dirPath );
			GenerateEnumTypesForAssembly( typeof( Microsoft.Office.Core.CommandBar ).Assembly, dirPath );
		}
		/// <summary>
		/// Generates enum types of Excel for MS Office wrapper.
		/// </summary>
		static void GenerateEnumTypesForAssembly_Excel() {
			string dirPath = GetDirPathFor_Excel();
			GenerateEnumTypesForAssembly( typeof( Microsoft.Office.Interop.Excel.Application ).Assembly, dirPath );
		}
		/// <summary>
		/// Generates enum types of Word for MS Office wrapper.
		/// </summary>
		static void GenerateEnumTypesForAssembly_Word() {
			string dirPath = GetDirPathFor_Word();
			GenerateEnumTypesForAssembly( typeof( Microsoft.Office.Interop.Word.Application ).Assembly, dirPath );
		}
		/// <summary>
		/// Generates enum types of specified assembly for COM wrapper.
		/// </summary>
		static void GenerateEnumTypesForAssembly( System.Reflection.Assembly assembly, string directoryPath ) {
			if ( null == assembly ) { throw new ArgumentNullException( "assembly" ); }

			CodeGenerator generator = CreateDefaultCodeGenerator();
			SetTypeMapFor_MSOffice_All( generator.Context.TypeMap, DEFAULT_NAMESPACE__MSOFFICE_CORE );
			NormalizeTypeMap_RemoveDelegates( generator.Context.TypeMap );
			NormalizeTypMap_RemoveRedundantTypes( generator.Context.TypeMap );
			FindAndSpecifyCollections( generator.Context );
			FindAndSpecifyApplicationTypes( generator.Context );
			FindAndSpecifyInheritance( generator.Context );
			SetDefaultApplicationCOMNames( generator.Context );

			ProjectGenerator pg = new ProjectGenerator();
			pg.TargetDirectory = directoryPath;
			pg.CodeGenerator = generator;

			pg.GenerateTypesFromTypeMap( assembly );
			int a = 0;
		}

		/// <summary>
		/// Sets default options for specified options object.
		/// </summary>
		/// <param name="options">Specified options object.</param>
		static void SetDefaultOptions( CodeGeneratorOptions options ) {
			options.GenerateInterfaceAsClass = true;
			options.IgnoreNotSupportedMembers = true;
			options.IgnoreNotSupportedTypes = true;
			options.IgnorePropertiesWithParameters = true;

			options.ApplicationTypes.Clear();
			options.ApplicationTypes.Add( typeof( Microsoft.Office.Interop.Access.Application ) );
			options.ApplicationTypes.Add( typeof( Microsoft.Office.Interop.Excel.Application ) );
			options.ApplicationTypes.Add( typeof( Microsoft.Office.Interop.Outlook.Application ) );
			options.ApplicationTypes.Add( typeof( Microsoft.Office.Interop.PowerPoint.Application ) );
			options.ApplicationTypes.Add( typeof( Microsoft.Office.Interop.Publisher.Application ) );
			options.ApplicationTypes.Add( typeof( Microsoft.Office.Interop.Word.Application ) );

			options.NamesOfIgnoredTypeMembers.Add( "Dispose" );
			options.NamesOfIgnoredTypeMembers.Add( "Finalize" );

			Type[] arr_VBIDE = typeof( VBIDE.AddIn ).Assembly.GetTypes();
			options.IgnoredTypes.Clear();
			options.IgnoredTypes.AddRange( arr_VBIDE );
		}

		/// <summary>
		/// Creates code generator with default options.
		/// </summary>
		/// <returns></returns>
		static CodeGenerator CreateDefaultCodeGenerator() {
			CodeGenerator ret = new CodeGenerator();
			SetDefaultOptions( ret.Options );

			//ret.BaseTypeFullNameResolution += new EventHandler<ResolutionEventArgs<Type, string>>( generator_BaseTypeFullNameResolution );
			ret.Context.DefaultBaseType = typeof( TcKs.MSOffice.Common.ChildObjectWrapperBase );
			ret.Options.NamesOfIgnoredTypeMembers.Add( "VBE" );
			ret.Options.NamesOfIgnoredTypeMembers.Add( "VBProject" );

			return ret;
		}

		static void generator_BaseTypeFullNameResolution( object sender, ResolutionEventArgs<Type, string> e ) {
			List<Type> lst_ApplicationTypes = new List<Type>( new Type[]{
				typeof(Microsoft.Office.Interop.Access.Application)
				, typeof(Microsoft.Office.Interop.Excel.Application)
				, typeof(Microsoft.Office.Interop.Outlook.Application)
				, typeof(Microsoft.Office.Interop.PowerPoint.Application)
				, typeof(Microsoft.Office.Interop.Publisher.Application)
				, typeof(Microsoft.Office.Interop.Word.Application)
			} );

			if ( lst_ApplicationTypes.Contains( e.Source ) ) {
				e.Result = typeof( TcKs.MSOffice.Common.ApplicationWrapperBase ).FullName;
			}
			else {
				e.Result = typeof( TcKs.MSOffice.Common.ChildObjectWrapperBase ).FullName;
			}
		}

		/// <summary>
		/// Sets default type map.
		/// </summary>
		/// <param name="typeMap">Map of types.</param>
		/// <param name="modelTypes">Original (wrapped) types.</param>
		/// <param name="defaultNamespace">Default namespace for generated (wrapper) types.</param>
		static void SetDefaultTypeMap( Dictionary<Type, GeneratedTypeInfo> typeMap, Type[] modelTypes, string defaultNamespace ) {
			foreach ( Type modelType in modelTypes ) {
				if ( false == typeMap.ContainsKey( modelType ) ) {
					GeneratedTypeInfo gti = new GeneratedTypeInfo( modelType, defaultNamespace, modelType.Name );
					typeMap[modelType] = gti;
				}
			}
		}

		/// <summary>
		/// Sets type map of MS Office core.
		/// </summary>
		/// <param name="typeMap">Map of types.</param>
		/// <param name="defaultNamespace">Default namespace for generated (wrapper) types.</param>
		static void SetTypeMapFor_MSOffice_Core( Dictionary<Type, GeneratedTypeInfo> typeMap, string defaultNamespace ) {
			Type[] arr_ModelTypes = typeof( stdole.Font ).Assembly.GetTypes();
			SetDefaultTypeMap( typeMap, arr_ModelTypes, defaultNamespace );

			//arr_ModelTypes = typeof( VBIDE.AddIn ).Assembly.GetTypes();
			//SetDefaultTypeMap( typeMap, arr_ModelTypes, defaultNamespace );

			arr_ModelTypes = typeof( Microsoft.Office.Core._CommandBarComboBox ).Assembly.GetTypes();
			SetDefaultTypeMap( typeMap, arr_ModelTypes, defaultNamespace );
		}

		/// <summary>
		/// Sets type map of Excel.
		/// </summary>
		/// <param name="typeMap">Map of types.</param>
		/// <param name="defaultNamespace">Default namespace for generated (wrapper) types.</param>
		static void SetTypeMapFor_MSOffice_Excel( Dictionary<Type, GeneratedTypeInfo> typeMap, string defaultNamespace ) {
			Type[] arr_ModelTypes = typeof( Microsoft.Office.Interop.Excel.AppEvents ).Assembly.GetTypes();
			SetDefaultTypeMap( typeMap, arr_ModelTypes, defaultNamespace );
		}
		/// <summary>
		/// Sets type map of Word.
		/// </summary>
		/// <param name="typeMap">Map of types.</param>
		/// <param name="defaultNamespace">Default namespace for generated (wrapper) types.</param>
		static void SetTypeMapFor_MSOffice_Word( Dictionary<Type, GeneratedTypeInfo> typeMap, string defaultNamespace ) {
			Type[] arr_ModelTypes = typeof( Microsoft.Office.Interop.Word.Application ).Assembly.GetTypes();
			SetDefaultTypeMap( typeMap, arr_ModelTypes, defaultNamespace );
		}
		/// <summary>
		/// Sets type map of whole MS Office assemblies.
		/// </summary>
		/// <param name="typeMap">Map of types.</param>
		/// <param name="defaultNamespace">Default namespace for generated (wrapper) types.</param>
		static void SetTypeMapFor_MSOffice_All( Dictionary<Type, GeneratedTypeInfo> typeMap, string defaultNamespace ) {
			SetTypeMapFor_MSOffice_Core( typeMap, defaultNamespace + ".Common" );
			SetTypeMapFor_MSOffice_Excel( typeMap, defaultNamespace + ".Excel" );
			SetTypeMapFor_MSOffice_Word( typeMap, defaultNamespace + ".Word" );
		}
		
		/// <summary>
		/// Find types which are collection and sets up the context.
		/// </summary>
		/// <param name="context">Context of code generations.</param>
		static void FindAndSpecifyCollections( CodeGeneratorContext context ) {
			Dictionary<string, Type> typeFullNames = new Dictionary<string, Type>( context.TypeMap.Count );
			foreach ( Type modelType in context.TypeMap.Keys ) {
				typeFullNames.Add( modelType.FullName, modelType );
			}

			//foreach ( string strTypeFullName in typeFullNames.Keys ) {
			foreach( KeyValuePair<string, Type> kp in typeFullNames ) {
				string strTypeFullName = kp.Key;
				if ( strTypeFullName.EndsWith( "s" ) ) {
					string strPropableItemTypeFullName;
					if ( strTypeFullName.EndsWith( "es" ) ) {
						strPropableItemTypeFullName = strTypeFullName.Substring( 0, strTypeFullName.Length - "es".Length );
					}
					else {
						strPropableItemTypeFullName = strTypeFullName.Substring( 0, strTypeFullName.Length - "s".Length );
					}

					Type tpItem;
					typeFullNames.TryGetValue( strPropableItemTypeFullName, out tpItem );
					if ( null != tpItem ) {
						TypeSpecification tsCollection = TypeSpecification.SpecifyCollection( tpItem );

						string[] arr_IgnoredMemberNames = new string[] { "Count", "Item" };
						foreach ( string strIgnoredMemberName in arr_IgnoredMemberNames ) {
							System.Reflection.MemberInfo[] arr_IgnoredMembers = kp.Value.GetMember( strIgnoredMemberName, BindingFlags.Instance | BindingFlags.Public );
							if ( null != arr_IgnoredMemberNames ) {
								foreach ( System.Reflection.MemberInfo mi_Ignored in arr_IgnoredMembers ) {
									if ( null == mi_Ignored ) { continue; }
									tsCollection.IgnoredMembers.Add( mi_Ignored );
								}
							}
						}

						context.TypeSpecifications.Add( kp.Value, tsCollection );
					}
				}
			}
		}

		/// <summary>
		/// Find types which are inherited from another non-base class.
		/// </summary>
		/// <param name="context">Context of code generations.</param>
		static void FindAndSpecifyInheritance( CodeGeneratorContext context ) {
			foreach ( Type tp in context.TypeMap.Keys ) {
				if ( tp.Name.StartsWith( "_" ) ) {
					string strDerivedName = tp.Name.Substring( 1 );
					Type tpDerived = null;
					foreach ( Type tpTmpDerived in context.TypeMap.Keys ) {
						if ( tpTmpDerived.Name == strDerivedName && tpTmpDerived.Namespace == tp.Namespace ) {
							tpDerived = tpTmpDerived;
							break;
						}
					}

					if ( null != tpDerived ) {
						TypeSpecification tsDerived;
						context.TypeSpecifications.TryGetValue( tpDerived, out tsDerived );
						if ( null == tsDerived ) {
							context.TypeSpecifications[tpDerived] = tsDerived = new TypeSpecification();
						}

						tsDerived.ForcedBaseType = tp;

						TypeSpecification tsBase;
						context.TypeSpecifications.TryGetValue( tp, out tsBase );
						if ( null == tsBase ) {
							context.TypeSpecifications[tp] = tsBase = new TypeSpecification();
						}

						tsBase.IsAbstractClass = true;
					}
				}
			}
		}

		/// <summary>
		/// Find types which are "Application" types.
		/// </summary>
		/// <param name="context">Context of code generations.</param>
		static void FindAndSpecifyApplicationTypes( CodeGeneratorContext context ) {
			foreach ( Type tp in context.TypeMap.Keys ) {
				if ( tp.Name == "_Application" ) {
					if ( false == context.ApplicationTypes.Contains( tp ) ) {
						context.ApplicationTypes.Add( tp );
					}
				}
				else if ( tp.Name == "Application" ) {
					Type tpBaseApplication = tp.GetInterface( "_Application" );
					if ( null != tpBaseApplication ) {
						if ( false == context.ApplicationTypes.Contains( tpBaseApplication ) ) {
							context.ApplicationTypes.Add( tpBaseApplication );
						}

						TypeSpecification tsApplication;
						context.TypeSpecifications.TryGetValue( tp, out tsApplication );
						if ( null == tsApplication ) {
							context.TypeSpecifications[tp] = tsApplication = new TypeSpecification();
						}

						tsApplication.ForcedBaseType = tpBaseApplication;

						TypeSpecification tsBaseApplication;
						context.TypeSpecifications.TryGetValue( tpBaseApplication, out tsBaseApplication );
						if ( null == tsBaseApplication ) {
							context.TypeSpecifications[tpBaseApplication] = tsBaseApplication = new TypeSpecification();
						}

						tsBaseApplication.IsAbstractClass = true;
					}
				}
			}
		}

		/// <summary>
		/// Set default MS Office "Application" type names.
		/// </summary>
		/// <param name="context">Context of code generations.</param>
		static void SetDefaultApplicationCOMNames( CodeGeneratorContext context ) {
			SetApplicationCOMName<Microsoft.Office.Interop.Access.Application>( "Access.Application", context );
			SetApplicationCOMName<Microsoft.Office.Interop.Excel.Application>( "Excel.Application", context );
			SetApplicationCOMName<Microsoft.Office.Interop.Outlook.Application>( "Outlook.Application", context );
			SetApplicationCOMName<Microsoft.Office.Interop.PowerPoint.Application>( "PowerPoint.Application", context );
			SetApplicationCOMName<Microsoft.Office.Interop.Publisher.Application>( "Publisher.Application", context );
			SetApplicationCOMName<Microsoft.Office.Interop.Word.Application>( "Word.Application", context );
		}

		/// <summary>
		/// Sets name of specified type as "Application" type.
		/// </summary>
		/// <typeparam name="T">Specified type.</typeparam>
		/// <param name="applicationCOMName">Name of COM type.</param>
		/// <param name="context">Context of code generations.</param>
		static void SetApplicationCOMName<T>( string applicationCOMName, CodeGeneratorContext context ) {
			SetApplicationCOMName( typeof( T ), applicationCOMName, context );
		}
		/// <summary>
		/// Sets name of specified type as "Application" type.
		/// </summary>
		/// <param name="type">Specified type.</param>
		/// <param name="applicationCOMName">Name of COM type.</param>
		/// <param name="context">Context of code generations.</param>
		static void SetApplicationCOMName( Type type, string applicationCOMName, CodeGeneratorContext context ) {
			TypeSpecification ts;
			context.TypeSpecifications.TryGetValue( type, out ts );
			if ( null == ts ) {
				context.TypeSpecifications[type] = ts = new TypeSpecification();
			}

			ts.ApplicationCOMName = applicationCOMName;
		}

		/// <summary>
		/// Removes from type map types which are enum types.
		/// </summary>
		/// <param name="typeMap">Type map.</param>
		static void NormalizeTypeMap_RemoveEnumTypes( Dictionary<Type, GeneratedTypeInfo> typeMap ) {
			Type[] arr_Types = new Type[typeMap.Count];
			typeMap.Keys.CopyTo( arr_Types, 0 );

			foreach ( Type tp in arr_Types ) {
				if ( tp.IsEnum ) { typeMap.Remove( tp ); }
			}
		}

		/// <summary>
		/// Removes from type map types which are delegate types.
		/// </summary>
		/// <param name="typeMap">Type map.</param>
		static void NormalizeTypeMap_RemoveDelegates( Dictionary<Type, GeneratedTypeInfo> typeMap ) {
			Type[] arr_Types = new Type[typeMap.Count];
			typeMap.Keys.CopyTo( arr_Types, 0 );

			Type tpDelegate = typeof( Delegate );
			foreach ( Type tp in arr_Types ) {
				if ( tpDelegate.IsAssignableFrom( tp ) ) { typeMap.Remove( tp ); }
			}
		}

		/// <summary>
		/// Removes from type map types which are redundant.
		/// </summary>
		/// <param name="typeMap">Type map.</param>
		static void NormalizeTypMap_RemoveRedundantTypes( Dictionary<Type, GeneratedTypeInfo> typeMap ) {
			Type[] arr_Types = new Type[typeMap.Count];
			typeMap.Keys.CopyTo( arr_Types, 0 );

			const string CLASS = "Class";

			foreach ( Type tp in arr_Types ) {
				if ( tp.IsClass && tp.Name.EndsWith( CLASS ) ) {
					string strInterfaceTypeName = tp.Name.Substring( 0, tp.Name.Length - "Class".Length );
					Type tp_Interface = tp.GetInterface( strInterfaceTypeName );
					if ( null != tp_Interface ) {
						typeMap.Remove( tp );
					}
				}
			}
		}

		/// <summary>
		/// Internal testing method.
		/// </summary>
		static void Test_GenerateCodeForTypeEnum() {
			Type tpEnum = typeof( Microsoft.Office.Core.DocProperties );
			Test_GenerateCodeForType( tpEnum );
		}

		/// <summary>
		/// Internal testing method.
		/// </summary>
		static void Test_GenerateCodeForType() {
			//Type tpRange = typeof( Microsoft.Office.Interop.Excel.Range );
			//Test_GenerateCodeForType( tpRange );

			Test_GenerateCodeForType<Microsoft.Office.Interop.Excel.Workbooks>();
		}

		/// <summary>
		/// Internal testing method.
		/// </summary>
		static void Test_GenerateCodeForType<T>() {
			Test_GenerateCodeForType( typeof( T ) );
		}

		/// <summary>
		/// Internal testing method.
		/// </summary>
		static void Test_GenerateCodeForType( Type tpModel ) {
			StringBuilder sbCode = new StringBuilder( 10000 );
			VbNetSourceCodeGenerator code = new VbNetSourceCodeGenerator( sbCode );
			CodeGenerator generator = CreateDefaultCodeGenerator();
			SetTypeMapFor_MSOffice_All( generator.Context.TypeMap, DEFAULT_NAMESPACE__MSOFFICE_CORE );
			FindAndSpecifyCollections( generator.Context );
			NormalizeTypMap_RemoveRedundantTypes( generator.Context.TypeMap );

			generator.GenerateCodeForType( tpModel, code );
			code.Flush();

			int a = 0;
		}

		/// <summary>
		/// Internal testing method.
		/// </summary>
		static void Test_GenerateCodeForTypeMember() {
			Type tpRange = typeof( Microsoft.Office.Interop.Excel.Range );
			PropertyInfo pi_AllowEdit = tpRange.GetProperty( "AllowEdit" );
			//PropertyInfo[] arr_Pis = tpRange.GetProperties( BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static );

			StringBuilder sbCode = new StringBuilder();
			VbNetSourceCodeGenerator code = new VbNetSourceCodeGenerator( sbCode );
			CodeGenerator generator = new CodeGenerator();
			generator.GenerateCodeForTypeMember( pi_AllowEdit, code );

			int a = 0;
		}

		/// <summary>
		/// Internal testing method.
		/// </summary>
		static void Test_VbGenerator() {
			StringBuilder sbCode = new StringBuilder();
			VbNetSourceCodeGenerator code = new VbNetSourceCodeGenerator( sbCode );
			code.Public.Class._( "Range" ).Line();
			using ( code.IndentBlock() ) {
				code.Inherits._( "WrapperBase" ).Line();
				code.Line();
				code.Friend.Sub.New._V.ByRef._( "nativeRange" ).As._<object>()._V.Line();
				using ( code.IndentBlock() ) {
					code.MyBase.New._V._( "nativeRange" )._V.Line();
				}
				code.End.Sub.Line();
				code.Line();
			}
			code.End.Class.Line();
			code.Flush();

			string str = sbCode.ToString();
			int a = 0;
		}
	}
}
